home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World 2008 September
/
PCWorld_2008-09_cd.bin
/
v cisle
/
sadanastroju
/
lightning-0.8-tb-win.xpi
/
chrome
/
calendar.jar
/
content
/
calendar
/
calendar-task-tree.xml
< prev
next >
Wrap
Extensible Markup Language
|
2008-03-03
|
48KB
|
1,167 lines
<?xml version="1.0"?>
<!-- ***** BEGIN LICENSE BLOCK *****
- Version: MPL 1.1/GPL 2.0/LGPL 2.1
-
- The contents of this file are subject to the Mozilla Public License Version
- 1.1 (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
- http://www.mozilla.org/MPL/
-
- Software distributed under the License is distributed on an "AS IS" basis,
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- for the specific language governing rights and limitations under the
- License.
-
- The Original Code is OEone Calendar Code, released October 31st, 2001.
-
- The Initial Developer of the Original Code is OEone Corporation.
- Portions created by the Initial Developer are Copyright (C) 2001
- the Initial Developer. All Rights Reserved.
-
- Contributor(s):
- Garth Smedley <garths@oeone.com>
- Mike Potter <mikep@oeone.com>
- Chris Charabaruk <coldacid@meldstar.com>
- Colin Phillips <colinp@oeone.com>
- ArentJan Banck <ajbanck@planet.nl>
- Curtis Jewell <csjewell@mail.freeshell.org>
- Eric Belhaire <eric.belhaire@ief.u-psud.fr>
- Mark Swaffer <swaff@fudo.org>
- Michael Buettner <michael.buettner@sun.com>
- Philipp Kewisch <mozilla@kewis.ch>
- Lars Wohlfahrt <thetux.moz@googlemail.com>
-
- Alternatively, the contents of this file may be used under the terms of
- either the GNU General Public License Version 2 or later (the "GPL"), or
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- in which case the provisions of the GPL or the LGPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of either the GPL or the LGPL, and not to allow others to
- use your version of this file under the terms of the MPL, indicate your
- decision by deleting the provisions above and replace them with the notice
- and other provisions required by the GPL or the LGPL. If you do not delete
- the provisions above, a recipient may use your version of this file under
- the terms of any one of the MPL, the GPL or the LGPL.
-
- ***** END LICENSE BLOCK ***** -->
<!DOCTYPE dialog [
<!ENTITY % dtd1 SYSTEM "chrome://calendar/locale/global.dtd" > %dtd1;
<!ENTITY % dtd2 SYSTEM "chrome://calendar/locale/calendar.dtd" > %dtd2;
]>
<bindings id="calendar-task-tree-bindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="calendar-task-tree">
<resources>
<stylesheet src="chrome://calendar/skin/calendar-task-tree.css"/>
</resources>
<content>
<xul:tree anonid="calendar-task-tree"
class="calendar-task-tree"
flex="1"
enableColumnDrag="false">
<xul:treecols anonid="calendar-task-tree-cols">
<xul:treecol anonid="calendar-task-tree-col-completed"
class="calendar-task-tree-col-completed"
minwidth="19"
fixed="true"
cycler="true"
label="&calendar.unifinder.tree.done.label;">
<xul:image anonid="checkboximg" />
</xul:treecol>
<xul:splitter class="tree-splitter"/>
<xul:treecol anonid="calendar-task-tree-col-priority"
class="calendar-task-tree-col-priority"
minwidth="17"
fixed="true"
label="&calendar.unifinder.tree.priority.label;">
<xul:image anonid="priorityimg"/>
</xul:treecol>
<xul:splitter class="tree-splitter"/>
<xul:treecol anonid="calendar-task-tree-col-title"
flex="1"
label="&calendar.unifinder.tree.title.label;"/>
<xul:splitter class="tree-splitter"/>
<xul:treecol anonid="calendar-task-tree-col-startdate"
flex="1" label="&calendar.unifinder.tree.startdate.label;"/>
<xul:splitter class="tree-splitter"/>
<xul:treecol anonid="calendar-task-tree-col-duedate"
flex="1" label="&calendar.unifinder.tree.duedate.label;"/>
<xul:splitter class="tree-splitter"/>
<xul:treecol anonid="calendar-task-tree-col-completeddate"
flex="1" label="&calendar.unifinder.tree.completeddate.label;"/>
<xul:splitter class="tree-splitter"/>
<xul:treecol anonid="calendar-task-tree-col-percentcomplete"
flex="1" label="&calendar.unifinder.tree.percentcomplete.label;"/>
<xul:splitter class="tree-splitter"/>
<xul:treecol anonid="calendar-task-tree-col-categories"
flex="1" label="&calendar.unifinder.tree.categories.label;"/>
<xul:splitter class="tree-splitter"/>
<xul:treecol anonid="calendar-task-tree-col-location"
label="&calendar.unifinder.tree.location.label;"/>
<xul:splitter class="tree-splitter"/>
<xul:treecol anonid="calendar-task-tree-col-status"
flex="1"
label="&calendar.unifinder.tree.status.label;"/>
<xul:splitter class="tree-splitter"/>
<xul:treecol anonid="calendar-task-tree-col-calendarname"
flex="1"
label="&calendar.unifinder.tree.calendarname.label;"/>
</xul:treecols>
<xul:treechildren tooltip="taskTreeTooltip"/>
</xul:tree>
</content>
<implementation>
<field name="mLoadCount">0</field>
<field name="mTaskArray">[]</field>
<field name="mHash2Index"><![CDATA[({})]]></field>
<field name="mContextTask">null</field>
<field name="mRefreshQueue">[]</field>
<field name="mPendingRefresh">null</field>
<field name="mHideCompletedTasks">false</field>
<field name="mFilterFunction">null</field>
<field name="mStartDate">null</field>
<field name="mEndDate">null</field>
<property name="currentIndex">
<getter><![CDATA[
var tree = document.getAnonymousElementByAttribute(
this, "anonid", "calendar-task-tree");
return tree.currentIndex;
]]></getter>
</property>
<property name="currentTask">
<getter><![CDATA[
var tree = document.getAnonymousElementByAttribute(
this, "anonid", "calendar-task-tree");
var index = tree.currentIndex;
return (index < 0) ? null : this.mTaskArray[index];
]]></getter>
</property>
<property name="selectedTasks" readonly="true">
<getter><![CDATA[
var tasks = [];
var start = {};
var end = {};
if (!this.mTreeView.selection) {
return tasks;
}
var rangeCount = this.mTreeView.selection.getRangeCount();
for (var range = 0; range < rangeCount; range++) {
this.mTreeView.selection.getRangeAt(range, start, end);
for (var i = start.value; i <= end.value; i++) {
var task = this.getTaskAtRow(i);
if (task) {
tasks.push(this.getTaskAtRow(i));
}
}
}
return tasks;
]]></getter>
</property>
<property name="hideCompleted">
<getter><![CDATA[
return this.mHideCompletedTasks;
]]></getter>
<setter><![CDATA[
this.mHideCompletedTasks = val;
return val;
]]></setter>
</property>
<property name="filterFunction">
<setter><![CDATA[
this.mFilterFunction = val;
return val;
]]></setter>
</property>
<property name="startDate">
<getter><![CDATA[
return this.mStartDate;
]]></getter>
<setter><![CDATA[
this.mStartDate = val;
return val;
]]></setter>
</property>
<property name="endDate">
<getter><![CDATA[
return this.mEndDate;
]]></getter>
<setter><![CDATA[
this.mEndDate = val;
return val;
]]></setter>
</property>
<method name="getTaskAtRow">
<parameter name="aRow"/>
<body><![CDATA[
return this.mTaskArray[aRow];
]]></body>
</method>
<method name="getTaskFromEvent">
<parameter name="aEvent"/>
<body><![CDATA[
return this.mTreeView._getItemFromEvent(aEvent);
]]></body>
</method>
<property name="contextTask">
<getter><![CDATA[
return this.mContextTask;
]]></getter>
<setter><![CDATA[
this.mContextTask = val;
return this.mContextTask;
]]></setter>
</property>
<field name="mTreeView"><![CDATA[
({
/**
* Attributes
*/
// back reference to the binding
binding: this,
tree: null,
treebox: null,
selectedColumn: null,
sortDirection: null,
// updated just before sort
sortStartedTime: now(),
/**
* High-level task tree manipulation
*/
addItem: function tTV_addItem(aItem) {
if (aItem.isCompleted && this.binding.hideCompleted) {
return;
}
var index = this.binding.mHash2Index[aItem.hashId];
if (index === undefined) {
var index = this.binding.mTaskArray.length;
this.binding.mTaskArray.push(aItem);
this.binding.mHash2Index[aItem.hashId] = index;
// The rowCountChanged function takes two arguments, the index where the
// first row was inserted and the number of rows to insert.
this.treebox.rowCountChanged(index, 1);
this.tree.view.selection.select(index);
}
this.treebox.ensureRowIsVisible(this.rowCount - 1);
},
removeItem: function tTV_removeItem(aItem) {
var index = this.binding.mHash2Index[aItem.hashId];
if (index != undefined) {
delete this.binding.mHash2Index[aItem.hashId];
this.binding.mTaskArray.splice(index, 1);
this.treebox.rowCountChanged(index, -1);
if (index == this.rowCount) {
index--;
}
this.tree.view.selection.select(index);
this.binding.recreateHashTable();
}
},
modifyItem: function tTV_modifyItem(aNewItem, aOldItem) {
var index = this.binding.mHash2Index[aOldItem.hashId];
if (index != undefined) {
// if a filter is installed we need to make sure that
// the item still belongs to the set of valid items before
// moving forward. if the filter cuts this item off, we
// need to act accordingly.
if (this.binding.mFilterFunction &&
!this.binding.mFilterFunction(aNewItem)) {
this.removeItem(aNewItem);
return;
}
// same holds true for the completed filter, which is
// currently modeled as an explicit boolean.
if (aNewItem.isCompleted != aOldItem.isCompleted) {
if (aNewItem.isCompleted && this.binding.hideCompleted) {
this.removeItem(aNewItem);
return;
}
}
delete this.binding.mHash2Index[aOldItem.hashId];
this.binding.mHash2Index[aNewItem.hashId] = index;
this.binding.mTaskArray[index] = aNewItem;
this.tree.view.selection.select(index);
this.treebox.invalidateRow(index);
}
},
clear: function tTV_clear() {
var count = this.binding.mTaskArray.length;
if (count > 0) {
this.binding.mTaskArray = [];
this.binding.mHash2Index = {};
this.treebox.rowCountChanged(0, -count);
this.tree.view.selection.clearSelection();
}
},
updateItem: function tTV_updateItem(aItem) {
var index = this.binding.mHash2Index[aItem.hashId];
if (index) {
this.treebox.invalidateRow(index);
}
},
/**
* nsITreeView methods and properties
*/
get rowCount() {
return this.binding.mTaskArray.length;
},
// An atomized list of properties for a given cell. Each property, x, that the
// view gives back will cause the pseudoclass :moz-tree-cell-x to be matched
// on the ::moz-tree-cell pseudoelement. It is use here to color a particular
// row with a color given by the progress state of the task.
getCellProperties: function mTV_getCellProperties(aRow, aCol, aProps) {
var task = this.binding.mTaskArray[aRow];
if (aCol.element.getAttribute("anonid") == "calendar-task-tree-col-priority") {
if(task.priority > 0 && task.priority < 5) {
aProps.AppendElement(getAtomFromService("highpriority"));
}
if(task.priority > 5 && task.priority < 10) {
aProps.AppendElement(getAtomFromService("lowpriority"));
}
}
aProps.AppendElement(getAtomFromService(this._progressAtom(task)));
if (aCol.element.hasAttribute("anonid")) {
aProps.AppendElement(getAtomFromService(aCol.element.getAttribute("anonid")));
}
},
// Called to get properties to paint a column background.
// For shading the sort column, etc.
getColumnProperties: function mTV_getColumnProperties() {
return false;
},
// An atomized list of properties for a given row. Each property, x, that the
// view gives back will cause the pseudoclass :moz-tree-row-x to be matched
// on the pseudoelement ::moz-tree-row. It is used here to color the background
// of a selected task with a color given by the progress state of the task.
getRowProperties: function mTV_getRowProperties(aRow, aProps) {
var task = this.binding.mTaskArray[aRow];
aProps.AppendElement(getAtomFromService(this._progressAtom(task)));
},
// Called on the view when a cell in a non-selectable cycling
// column (e.g., unread/flag/etc.) is clicked.
cycleCell: function mTV_cycleCell(aRow, aCol) {
var task = this.binding.mTaskArray[aRow];
if(!task)
return;
if (aCol != null) {
var anonid = aCol.element.getAttribute("anonid");
if (anonid == "calendar-task-tree-col-completed") {
var newTask = task.clone().QueryInterface(Components.interfaces.calITodo);
newTask.isCompleted = !task.completedDate;
doTransaction('modify', newTask, newTask.calendar, task, null);
}
}
},
// Called on the view when a header is clicked.
cycleHeader: function mTV_cycleHeader(aCol) {
var element = aCol.element;
var sortActive = element.getAttribute("sortActive");
this.selectedColumn = element.getAttribute("anonid");
this.sortDirection = element.getAttribute("sortDirection");
var treeCols;
if (sortActive != "true") {
var treecols = document.getAnonymousNodes(
this.binding)[0].getElementsByTagName("treecol");
for (var i = 0; i < treecols.length; i++) {
treecols[i].removeAttribute("sortActive");
treecols[i].removeAttribute("sortDirection");
}
sortActive = true;
this.sortDirection = "ascending";
}
else {
sortActive = true;
if(this.sortDirection == "" || this.sortDirection == "descending") {
this.sortDirection = "ascending";
} else {
this.sortDirection = "descending";
}
}
element.setAttribute("sortActive", sortActive);
element.setAttribute("sortDirection", this.sortDirection);
this.sortStartedTime = now(); // for null dates during sort
this.binding.sortTaskArray();
this.binding.recreateHashTable();
},
// The text for a given cell. If a column consists only of an
// image, then the empty string is returned.
getCellText: function mTV_getCellText(aRow, aCol) {
var task = this.binding.mTaskArray[aRow];
if (!task)
return false;
switch(aCol.element.getAttribute("anonid")) {
case "calendar-task-tree-col-title":
// return title, or "Untitled" if empty/null
return task.title || gCalendarBundle.getString("eventUntitled");
case "calendar-task-tree-col-startdate":
return this._formatDateTime(task.entryDate);
case "calendar-task-tree-col-duedate":
return this._formatDateTime(task.dueDate);
case "calendar-task-tree-col-completeddate":
return this._formatDateTime(task.completedDate);
case "calendar-task-tree-col-percentcomplete":
return task.percentComplete+"%";
case "calendar-task-tree-col-categories":
return task.getProperty("CATEGORIES");
case "calendar-task-tree-col-location":
return task.getProperty("LOCATION");
case "calendar-task-tree-col-status":
return getToDoStatusString(task);
case "calendar-task-tree-col-calendarname":
return task.calendar.name;
case "calendar-task-tree-col-completed":
case "calendar-task-tree-col-priority":
default:
return "";
}
},
// This method is only called for columns of type other than text.
getCellValue: function mTV_getCellValue(aRow, aCol) {
return null;
},
// SetCellValue is called when the value of the cell has been set by the user.
// This method is only called for columns of type other than text.
setCellValue: function mTV_setCellValue(aRow, aCol, aValue) {
return null;
},
// The image path for a given cell. For defining an icon for a cell.
// If the empty string is returned, the :moz-tree-image pseudoelement will be used.
getImageSrc: function mTV_getImageSrc(aRow, aCol) {
// Return the empty string in order
// to use moz-tree-image pseudoelement :
// it is mandatory to return "" and not false :-(
return("");
},
// IsEditable is called to ask the view if the cell contents are editable.
// A value of true will result in the tree popping up a text field when the user
// tries to inline edit the cell.
isEditable: function mTV_isEditable(aRow, aCol) {
return true;
},
// Called during initialization to link the view to the front end box object.
setTree: function mTV_setTree(aTreeBox) {
this.treebox = aTreeBox;
},
// Methods that can be used to test whether or not a twisty should
// be drawn, and if so, whether an open or closed twisty should be used.
isContainer: function mTV_isContainer(aRow) {
return false;
},
// IsSeparator is used to determine if the row at index is a separator.
// A value of true will result in the tree drawing a horizontal separator.
// The tree uses the ::moz-tree-separator pseudoclass to draw the separator.
isSeparator: function mTV_isSeparator(aRow) {
return false;
},
// Specifies if there is currently a sort on any column.
// Used mostly by dragdrop to affect drop feedback.
isSorted: function mTV_isSorted(aRow) {
return false;
},
// The level is an integer value that represents the level of indentation.
// It is multiplied by the width specified in the :moz-tree-indentation
// pseudoelement to compute the exact indendation.
getLevel: function mTV_getLevel(aRow) {
return 0;
},
canDrop: function mTV_canDrop() { return false; },
/**
* Task Tree Events
*/
onSelect: function tTV_onSelect(event) {},
onDoubleClick: function tTV_onDoubleClick(event) {
if (event.button == 0) {
var col = {};
var item = this._getItemFromEvent(event, col);
if (item) {
var colAnonId = col.value.element.getAttribute("anonid");
if (colAnonId == "calendar-task-tree-col-completed") {
// item holds checkbox state toggled by first click,
// so don't call modifyEventWithDialog
// to make sure user notices state changed.
} else {
modifyEventWithDialog(item);
}
} else {
createTodoWithDialog();
}
}
},
drop: function tTV_drop(aRow, aOrientation) {},
getParentIndex: function tTV_getParentIndex(aRow) {
return -1;
},
onKeyPress: function tTV_onKeyPress(event) {
const kKE = Components.interfaces.nsIDOMKeyEvent;
switch (event.keyCode || event.which) {
case kKE.DOM_VK_DELETE:
document.popupNode = this.binding;
document.getElementById('calendar_delete_todo_command').doCommand();
event.preventDefault();
event.stopPropagation();
break;
case kKE.DOM_VK_SPACE:
if (this.tree.currentIndex > -1) {
var col = document.getAnonymousElementByAttribute(
this.binding, "anonid", "calendar-task-tree-col-completed");
this.cycleCell(
this.tree.currentIndex,
{ element: col });
}
break;
case kKE.DOM_VK_RETURN:
var index = this.tree.currentIndex;
if (index > 0) {
modifyEventWithDialog(this.binding.mTaskArray[index]);
}
break;
}
},
// Set the context menu on mousedown to change it before it is opened
onMouseDown: function tTV_onMouseDown(event) {
var tree = document.getAnonymousElementByAttribute(
this.binding, "anonid", "calendar-task-tree");
var treechildren = tree.getElementsByTagName("treechildren")[0];
if (!this._getItemFromEvent(event)) {
tree.view.selection.clearSelection();
}
},
/**
* Private methods and attributes
*/
_getItemFromEvent: function tTV_getItemFromEvent(event, aCol, aRow) {
aRow = aRow || {};
var childElt = {};
this.treebox.getCellAt(event.clientX, event.clientY, aRow, aCol || {}, childElt);
if (!childElt.value) {
return false;
}
return aRow && aRow.value > -1 && this.binding.mTaskArray[aRow.value];
},
// This function return the progress state of a task :
// completed, overdue, duetoday, inprogress, future
_progressAtom: function tTV_progressAtom(aTask) {
var now = new Date();
if (aTask.isCompleted)
return "completed";
if (aTask.dueDate && aTask.dueDate.isValid) {
if (aTask.dueDate.jsDate.getTime() < now.getTime())
return "overdue";
else if (aTask.dueDate.year == now.getFullYear() &&
aTask.dueDate.month == now.getMonth() &&
aTask.dueDate.day == now.getDate())
return "duetoday";
}
if (aTask.entryDate && aTask.entryDate.isValid &&
aTask.entryDate.jsDate.getTime() < now.getTime())
return "inprogress";
return "future";
},
// Helper function to display datetimes
_formatDateTime: function tTV_formatDateTime(aDateTime) {
var dateFormatter = Components.classes["@mozilla.org/calendar/datetime-formatter;1"]
.getService(Components.interfaces.calIDateTimeFormatter);
// datetime is from todo object, it is not a javascript date
if (aDateTime && aDateTime.isValid) {
var dateTime = aDateTime.getInTimezone(calendarDefaultTimezone());
return dateFormatter.formatDateTime(dateTime);
}
return "";
}
})
]]></field>
<!--
Observer for the calendar event data source. This keeps the unifinder
display up to date when the calendar event data is changed
-->
<field name="mTaskTreeObserver"><![CDATA[
({
binding: this,
mInBatch: false,
QueryInterface: function tTO_QueryInterface(aIID) {
ensureIID(
[ Components.interfaces.calICompositeObserver,
Components.interfaces.calIObserver,
Components.interfaces.nsISupports], aIID);
return this;
},
/**
* calIObserver methods and properties
*/
onStartBatch: function tTO_onStartBatch() {
this.mInBatch = true;
},
onEndBatch: function tTO_onEndBatch() {
if (this.mInBatch) {
this.mInBatch = false;
this.binding.refresh();
}
},
onLoad: function tTO_onLoad() {
if (!this.mInBatch) {
this.binding.refresh();
}
},
onAddItem: function tTO_onAddItem(aItem) {
if (aItem instanceof Components.interfaces.calITodo && !this.mInBatch) {
this.binding.mTreeView.addItem(aItem);
}
},
onModifyItem: function tTO_onModifyItem(aNewItem, aOldItem) {
if ((aNewItem instanceof Components.interfaces.calITodo ||
aOldItem instanceof Components.interfaces.calITodo) &&
!this.mInBatch) {
// forward the call to the view which will in turn
// update the internal reference and the view.
this.binding.mTreeView.modifyItem(aNewItem, aOldItem);
// we also need to notify potential listeners.
var event = document.createEvent('Events');
event.initEvent('select', true, false);
this.binding.dispatchEvent(event);
}
},
onDeleteItem: function tTO_onDeleteItem(aDeletedItem) {
if (aDeletedItem instanceof Components.interfaces.calITodo &&
!this.mInBatch) {
this.binding.mTreeView.removeItem(aDeletedItem);
}
},
onError: function tTO_onError(aErrNo, aMessage) {},
onPropertyChanged: function tTO_onPropertyChanged(aCalendar, aName, aValue, aOldValue) {},
onPropertyDeleting: function tTO_onPropertyDeleting(aCalendar, aName) {},
/**
* calICompositeObserver methods and properties
*/
onCalendarAdded: function tTO_onCalendarAdded(aCalendar) {
if (!this.mInBatch) {
this.binding.onCalendarAdded(aCalendar);
}
},
onCalendarRemoved: function tTO_onCalendarRemoved(aCalendar) {
if (!this.mInBatch) {
this.binding.onCalendarRemoved(aCalendar);
}
},
onDefaultCalendarChanged: function tTO_onDefaultCalendarChanged(aNewDefaultCalendar) {}
})
]]></field>
<constructor><![CDATA[
var self = this;
var load = function tTV_loadHandler() {
self.onLoad();
};
window.addEventListener("load", load, true);
var unload = function tTV_unloadHandler() {
self.onUnload();
};
window.addEventListener("unload", unload, true);
// we want to make several attributes on the column
// elements persistent, but unfortunately there's no
// relyable way with the 'persist' feature.
// that's why we need to store the necessary bits and
// pieces at the element this binding is attached to.
var names = this.getAttribute("visible-columns").split(' ');
var ordinals = this.getAttribute("ordinals").split(' ');
var widths = this.getAttribute("widths").split(' ');
var sortActive = this.getAttribute("sort-active");
var sortDirection = this.getAttribute("sort-direction") || "ascending";
var treecols = document.getAnonymousNodes(
this)[0].getElementsByTagName("treecol");
for (var i = 0; i < treecols.length; i++) {
var anonid = treecols[i].getAttribute("anonid");
if (names.some(
function(element) {
var pos = anonid.length - element.length;
return (anonid.indexOf(element) == pos);
})) {
treecols[i].removeAttribute("hidden");
} else {
treecols[i].setAttribute("hidden","true");
}
if (ordinals && ordinals.length > 0) {
treecols[i].ordinal = Number(ordinals.shift());
}
if (widths && widths.length > 0) {
treecols[i].width = Number(widths.shift());
}
if (sortActive && sortActive.length > 0) {
var pos = anonid.length - sortActive.length;
if (anonid.indexOf(sortActive) == pos) {
treecols[i].setAttribute("sortActive", "true");
treecols[i].setAttribute("sortDirection", sortDirection);
}
}
}
]]></constructor>
<method name="onLoad">
<body><![CDATA[
if (!this.mLoadCount++) {
// set up the custom tree view
var tree = document.getAnonymousElementByAttribute(this, "anonid", "calendar-task-tree");
this.mTreeView.tree = tree;
tree.view = this.mTreeView;
// set up our calendar event observer
var composite = getCompositeCalendar();
composite.addObserver(this.mTaskTreeObserver);
}
]]></body>
</method>
<method name="onUnload">
<body><![CDATA[
if (!--this.mLoadCount) {
// remove out calendar event observer
var composite = getCompositeCalendar();
composite.removeObserver(this.mTaskTreeObserver);
var widths = "";
var ordinals = "";
var visible = "";
var sortActive = null;
var sortDirection = null;
var treecols = document.getAnonymousNodes(
this)[0].getElementsByTagName("treecol");
for (var i = 0; i < treecols.length; i++) {
if (treecols[i].getAttribute("hidden") != "true") {
var anonid = treecols[i].getAttribute("anonid");
anonid = anonid.substr(anonid.lastIndexOf("-")+1);
visible += (visible.length > 0) ? " "+anonid : anonid;
}
if(ordinals.length > 0)
ordinals += " ";
ordinals += treecols[i].ordinal;
if(widths.length > 0)
widths += " ";
widths += treecols[i].width || 0;
if (treecols[i].getAttribute("sortActive") == "true") {
sortActive = treecols[i].getAttribute("anonid");
sortActive = sortActive.substr(sortActive.lastIndexOf("-")+1);
sortDirection = treecols[i].getAttribute("sortDirection");
}
}
this.setAttribute("visible-columns",visible);
this.setAttribute("ordinals",ordinals);
this.setAttribute("widths",widths);
if (sortActive) {
this.setAttribute("sort-active",sortActive);
this.setAttribute("sort-direction",sortDirection);
} else {
this.removeAttribute("sort-active");
this.removeAttribute("sort-direction");
}
}
]]></body>
</method>
<!-- Called by event observers to update the display -->
<method name="refresh">
<parameter name="aFilter"/>
<body><![CDATA[
var savedThis = this;
var refreshListener = {
mTaskArray: [],
onOperationComplete: function(aCalendar, aStatus, aOperationType, aId, aDateTime) {
savedThis.mTaskArray = this.mTaskArray;
savedThis.onOperationComplete();
},
onGetResult: function(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
for (var i=0; i<aCount; i++) {
if (!savedThis.mFilterFunction ||
savedThis.mFilterFunction(aItems[i])) {
refreshListener.mTaskArray.push(aItems[i]);
}
}
}
};
var refreshJob = {
execute: function() {
var composite = getCompositeCalendar();
var filter = aFilter || savedThis.mHideCompletedTasks ?
composite.ITEM_FILTER_COMPLETED_NO :
composite.ITEM_FILTER_COMPLETED_ALL;
filter |= composite.ITEM_FILTER_TYPE_TODO;
if (savedThis.startDate && savedThis.endDate) {
filter |= composite.ITEM_FILTER_CLASS_OCCURRENCES;
}
composite.getItems(filter, 0, savedThis.startDate, savedThis.endDate, refreshListener);
}
};
this.mRefreshQueue.push(refreshJob);
this.popRefreshQueue();
]]></body>
</method>
<method name="onCalendarAdded">
<parameter name="aCalendar"/>
<parameter name="aFilter"/>
<body><![CDATA[
var savedThis = this;
var refreshListener = {
onOperationComplete: function (aCalendar, aStatus, aOperationType, aId, aDateTime) {
savedThis.onOperationComplete();
},
onGetResult: function (aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
for (var i=0; i<aCount; i++) {
if (!savedThis.mFilterFunction ||
savedThis.mFilterFunction(aItems[i])) {
savedThis.mTaskArray.push(aItems[i]);
}
}
}
};
var refreshJob = {
execute: function() {
var composite = getCompositeCalendar();
var filter = aFilter || savedThis.mHideCompletedTasks ?
composite.ITEM_FILTER_COMPLETED_NO :
composite.ITEM_FILTER_COMPLETED_ALL;
filter |= composite.ITEM_FILTER_TYPE_TODO;
if (savedThis.startDate && savedThis.endDate) {
filter |= composite.ITEM_FILTER_CLASS_OCCURRENCES;
}
aCalendar.getItems(filter, 0, savedThis.startDate, savedThis.endDate, refreshListener);
}
};
this.mRefreshQueue.push(refreshJob);
this.popRefreshQueue();
]]></body>
</method>
<method name="onCalendarRemoved">
<parameter name="aCalendar"/>
<body><![CDATA[
if (this.mTaskArray.length > 0) {
var index = this.mTaskArray.length - 1;
while(index >= 0) {
var item = this.mTaskArray[index];
if (item.calendar == aCalendar) {
this.mTreeView.removeItem(item);
}
--index;
}
}
// we also need to notify potential listeners.
var event = document.createEvent('Events');
event.initEvent('select', true, false);
this.dispatchEvent(event);
]]></body>
</method>
<method name="popRefreshQueue">
<body><![CDATA[
var pendingRefresh = this.mPendingRefresh;
if (pendingRefresh) {
if (pendingRefresh instanceof Components.interfaces.calIOperation) {
this.mPendingRefresh = null;
pendingRefresh.cancel(null);
} else {
return;
}
}
var refreshJob = this.mRefreshQueue.pop();
if (!refreshJob) {
return;
}
this.mPendingRefresh = true;
pendingRefresh = refreshJob.execute();
if (pendingRefresh && pendingRefresh.isPending) {
this.mPendingRefresh = pendingRefresh;
}
]]></body>
</method>
<method name="onOperationComplete">
<body><![CDATA[
// signal that the current operation finished.
this.mPendingRefresh = null;
// immediately start the next job on the queue.
this.popRefreshQueue();
this.mTreeView.rowCount = this.mTaskArray.length;
var treecols = document.getAnonymousNodes(this)[0].getElementsByTagName("treecol");
for (var i = 0; i < treecols.length; i++) {
if (treecols[i].getAttribute("sortActive") == "true") {
this.mTreeView.selectedColumn = treecols[i].getAttribute("anonid");
this.mTreeView.sortDirection = treecols[i].getAttribute("sortDirection");
this.mTreeView.sortStartedTime = now(); //for null/0 dates
this.sortTaskArray();
break;
}
}
this.recreateHashTable();
var tree = document.getAnonymousElementByAttribute(
this, "anonid", "calendar-task-tree");
tree.view = this.mTreeView;
// we also need to notify potential listeners.
var event = document.createEvent('Events');
event.initEvent('select', true, false);
this.dispatchEvent(event);
]]></body>
</method>
<method name="sortTaskArray">
<body><![CDATA[
var kStatusOrder = [
"NEEDS-ACTION",
"IN-PROCESS",
"COMPLETED",
"CANCELLED" ];
function compareString(a, b) {
a = nullToEmpty(a);
b = nullToEmpty(b);
return ((a < b) ? -1 :
(a > b) ? 1 : 0);
}
function nullToEmpty(value) {
return value == null? "" : value;
}
function compareNumber(a, b) {
// Number converts a date to msecs since 1970, and converts a null to 0.
a = Number(a);
b = Number(b);
return ((a < b) ? -1 : // avoid underflow problems of subtraction
(a > b) ? 1 : 0);
}
var sortStartedTime = this.mTreeView.sortStartedTime;
var sortDirection = this.mTreeView.sortDirection;
var selectedColumn = this.mTreeView.selectedColumn;
// Takes two calDateTimes
function compareDate(a, b) {
if (!a)
a = sortStartedTime;
if (!b)
b = sortStartedTime;
return a.compare(b);
}
function compareItems(a, b) {
var modifier = (sortDirection == "descending") ? -1 : 1;
switch(selectedColumn) {
case "calendar-task-tree-col-priority": // 0-9
// No priority set (0) sorts before high priority (1).
return compareNumber(a.priority, b.priority) * modifier;
case "calendar-task-tree-col-title":
return compareString(a.title.toLowerCase(), b.title.toLowerCase()) * modifier;
case "calendar-task-tree-col-startdate":
return compareDate(a.entryDate, b.entryDate) * modifier;
case "calendar-task-tree-col-duedate":
return compareDate(a.dueDate, b.dueDate) * modifier;
case "calendar-task-tree-col-completed": // checkbox if date exists
case "calendar-task-tree-col-completeddate":
return compareDate(a.completedDate, b.completedDate) * modifier;
case "calendar-task-tree-col-percentcomplete":
return compareNumber(a.percentComplete, b.percentComplete) * modifier;
case "calendar-task-tree-col-categories":
return compareString(a.getProperty("CATEGORIES"), b.getProperty("CATEGORIES")) * modifier;
case "calendar-task-tree-col-location":
return compareString(a.getProperty("LOCATION"), b.getProperty("LOCATION")) * modifier;
case "calendar-task-tree-col-status":
return compareNumber(kStatusOrder.indexOf(a.status), kStatusOrder.indexOf(b.status)) * modifier;
case "calendar-task-tree-col-calendarname":
return compareString(a.calendar.name, b.calendar.name) * modifier;
default:
return 0;
}
}
// use above local defined functor
this.mTaskArray.sort(compareItems);
var tree = document.getAnonymousElementByAttribute(this, "anonid", "calendar-task-tree");
if (tree.boxObject.invalidateRange) {
tree.boxObject.invalidateRange(0, this.mTaskArray.length - 1);
}
]]></body>
</method>
<method name="recreateHashTable">
<body><![CDATA[
this.mHash2Index = {};
for (var i=0; i<this.mTaskArray.length; i++) {
var item = this.mTaskArray[i];
this.mHash2Index[item.hashId] = i;
}
]]></body>
</method>
</implementation>
<handlers>
<handler event="select"><![CDATA[
this.mTreeView.onSelect(event);
]]></handler>
<handler event="dblclick" button="0"><![CDATA[
this.mTreeView.onDoubleClick(event);
]]></handler>
<handler event="keypress"><![CDATA[
this.mTreeView.onKeyPress(event);
]]></handler>
<handler event="mousedown"><![CDATA[
this.mTreeView.onMouseDown(event);
]]></handler>
<handler event="draggesture"><![CDATA[
var item = this.mTreeView._getItemFromEvent(event);
if (!item || item.calendar.readOnly) {
return;
}
var tree = document.getAnonymousElementByAttribute(this, "anonid", "calendar-task-tree");
// let's build the drag region
var region = null;
try {
region = Components.classes["@mozilla.org/gfx/region;1"].createInstance(Components.interfaces.nsIScriptableRegion);
region.init();
var obo = tree.treeBoxObject;
var bo = obo.treeBody.boxObject;
var sel= tree.view.selection;
var rowX = bo.x;
var rowY = bo.y;
var rowHeight = obo.rowHeight;
var rowWidth = bo.width;
//add a rectangle for each visible selected row
for (var i = obo.getFirstVisibleRow(); i <= obo.getLastVisibleRow(); i ++) {
if (sel.isSelected(i))
region.unionRect(rowX, rowY, rowWidth, rowHeight);
rowY = rowY + rowHeight;
}
//and finally, clip the result to be sure we don't spill over...
if(!region.isEmpty())
region.intersectRect(bo.x, bo.y, bo.width, bo.height);
} catch(ex) {
ASSERT(false, "Error while building selection region: " + ex + "\n");
region = null;
}
var dragService = Components.classes["@mozilla.org/widget/dragservice;1"].
getService(Components.interfaces.nsIDragService);
var transfer = Components.classes["@mozilla.org/widget/transferable;1"].
createInstance(Components.interfaces.nsITransferable);
transfer.addDataFlavor("text/calendar");
var serializer = Components.classes["@mozilla.org/calendar/ics-serializer;1"].
createInstance(Components.interfaces.calIIcsSerializer);
serializer.addItems([item], 1);
var supportsString = Components.classes["@mozilla.org/supports-string;1"].
createInstance(Components.interfaces.nsISupportsString);
supportsString.data = serializer.serializeToString();
transfer.setTransferData("text/calendar", supportsString, supportsString.data.length*2);
transfer.setTransferData("text/unicode", supportsString, supportsString.data.length*2);
var action = dragService.DRAGDROP_ACTION_MOVE;
var supArray = Components.classes["@mozilla.org/supports-array;1"].
createInstance(Components.interfaces.nsISupportsArray);
supArray.AppendElement(transfer);
dragService.invokeDragSession(event.target, supArray, region, action);
]]></handler>
</handlers>
</binding>
</bindings>